package breadbox.pastry;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

import breadbox.pastry.messages.EmptySystemMessage;
import breadbox.pastry.messages.JoinRequest;
import breadbox.pastry.messages.JoinResponse;
import breadbox.pastry.messages.MessageSerializer;
import breadbox.pastry.messages.RouteRepairMessage;
import breadbox.pastry.messages.StateMessage;
import breadbox.pastry.routing.LeafSet;
import breadbox.pastry.routing.NeighborhoodSet;
import breadbox.pastry.routing.RoutingNode;
import breadbox.pastry.routing.RoutingTable;

/*
 * The PastryNode represents the  
 */
public class PastryNode implements Node, Serializable {
	private static final long serialVersionUID = 6745193750805582943L;

	// Singleton node
	private static PastryNode node;
	private RoutingTable routing_table_;
	private NeighborhoodSet neighbors_;
	private LeafSet leafs_;
	private RoutingNode this_node_;
	private ID nodeID;
	transient private boolean open = false;
	transient private Application application = null;
	transient private Timer heartbeat = null;

	private PastryNode() {
		Conf.readConf("test.cfg");
		this.open = false;
		nodeID = new LongID((long) (Math.random() * Long.MAX_VALUE));
		this_node_ = new RoutingNode(nodeID);
		this_node_.setPort(Conf.myPort());
		routing_table_ = new RoutingTable();
		routing_table_.setBaseNode(new RoutingNode(this_node_));
		leafs_ = new LeafSet(new RoutingNode(this_node_));
		neighbors_ = new NeighborhoodSet(new RoutingNode(this_node_));
	}

	public static PastryNode getPastryNode() {
		if (node == null) {
			// Check if node has old data
			// If so, reload old data to bootstrap node
			// else use IP multicast or approx. technique
			// to bootstrap system - may initially be an argument
			File state = new File("node.sav");
			if (state.canRead()) {
				try {
					FileInputStream fis = new FileInputStream(state);
					ObjectInputStream ois = new ObjectInputStream(fis);
					PastryNode tempNode = (PastryNode) ois.readObject();
					node = tempNode;
					Conf.readConf("test.cfg");
					return node;
				} catch (FileNotFoundException e) {
					e.printStackTrace();
				} catch (IOException e) {
					e.printStackTrace();
				} catch (ClassNotFoundException e) {
					e.printStackTrace();
				}
			}
			node = new PastryNode();
		}
		return node;
	}

	public int open() {
		// Open up node for connections
		if (!this.open) {
			ServerSocket s = null;
			try {
				s = new ServerSocket();
				s.setSoTimeout(100);
				s.setReuseAddress(true);
				// s.bind(new InetSocketAddress("128.208.7.71", Conf.myPort()));
				s.bind(new InetSocketAddress(Conf.myPort()));
			} catch (IOException iex) {
				iex.printStackTrace();
				return 0;
			}
			new Thread(new ServerSocketThread(s)).start();
			
			heartbeat = new Timer();
			TimerTask heart = new Heart();
			heartbeat.schedule(heart, Conf.pulse(), Conf.pulse());
			
			TimerTask state = new StateSaver();
			heartbeat.schedule(state, 0, Conf.savePulse());
		}
		return 0;
	}

	public int join() {
		try {
			// TODO initialize port
			JoinRequest join = new JoinRequest(this_node_, Conf.bootstrapIP());
			join.origin.setPort(Conf.myPort());
			Message jm = new Message();
			jm.data = MessageSerializer.serialize(join);
			jm.from = nodeID;
			jm.to = nodeID;
			jm.type = PastryMessageType.JOIN_REQUEST;

			// TODO provide way for it to find bootstrap nodes,
			Connection bootstrap = new Connection();
			bootstrap.setIp(Conf.bootstrapIP());
			bootstrap.setPort(Conf.bootstrapPort());
			sendInternal(jm, bootstrap);
		} catch (IOException e) {
			e.printStackTrace();
		}
		return 0;
	}

	public int close() {
		// close node
		if (this.open) {
			this.open = false;
			
			if (heartbeat != null) {
				heartbeat.cancel();
			}
		}

		return 0;
	}

	public void register(Application app) {
		this.application = app;
		application.newLeaf(new LeafSet(leafs_));
	}

	public void unregister() {
		if (this.open) {
			this.close();
		}
		this.application = null;
	}

	public int route(Message m) {
		// Enforce typing on message(i.e. non-system message)\
		m.type = PastryMessageType.USER;
		routeAsync(m, null);
		return 0;
	}

	public int send(Message m, Connection c) throws IOException {
		// Enforce typing on message(i.e. non-system message)
		m.type = PastryMessageType.USER;
		return sendInternal(m, c);
	}

	public int proximity(RoutingNode n) {
		if (n != null) {
			return application.proximity(n.getIp(), n.getPort());
		} else {
			return -1;
		}
	}
	
	public ID getNodeId() {
		return this.nodeID;
	}

	private void routeAsync(Message m, Connection c) {
		Router r = new Router(m, c);
		new Thread(r).start();
	}
	
	private int routeInternal(Message m, Connection conn) {
		if (m.from != null)
			System.out.println("Message from: " + m.from.toString() + " Type: " + m.key);

		RoutingNode target = new RoutingNode(m.to);
		RoutingNode dest = null;

		// check for target in leaf set
		if (leafs_.spans(target)) {
			dest = leafs_.closest(target);
		} else {
			// check routing table
			int prefix_len = this_node_.getId().commonPrefix(m.to).length();

			dest = routing_table_.get(prefix_len + 1, m.to.toString().charAt(
					prefix_len + 1));

			// if no entry in the routing table, collect all possible options
			// and choose one at random.
			/// TODO change to check prefix as well
			if (dest == null) {
				List<RoutingNode> options = new ArrayList<RoutingNode>();
				long distance = nodeID.distance(m.to);

				// options from routing table
				for (int i = 0; i < routing_table_.rows(); i++) {
					for (int j = 0; j < routing_table_.cols(); j++) {
						RoutingNode n = routing_table_.get(i, j);

						if (n != null && n.getId().distance(m.to) < distance) {
							options.add(n);
						}
					}
				}

				// from leaf set
				RoutingNode[] leafs = leafs_.toArray();

				if (leafs != null) {
					for (RoutingNode n : leafs) {
						if (n != null && n.getId().distance(m.to) < distance) {
							options.add(n);
						}
					}
				}

				// from neighborhood set
				RoutingNode[] neighbors = neighbors_.toArray();

				if (neighbors != null) {
					for (RoutingNode n : neighbors) {
						if (n != null && n.getId().distance(m.to) < distance) {
							options.add(n);
						}
					}
				}

				// choose random target
				if (options.size() > 0) {
					int rand = ((int)(Math.random() * Integer.MAX_VALUE) % options.size());
					dest = options.get(rand);
				} else {
					// otherwise, this node must be the closest node of the ones
					// that
					// this node knows about
					dest = this_node_;
				}
			}
		}

		if (dest != null && !dest.equals(this_node_)) {
			// route message
			Connection c = new Connection(dest.getIp(), dest.getPort());

			// modify the message, execute side-effects
			if (m.type == PastryMessageType.USER) {
				application.forward(m);
			} else {
				onForward(m, conn);
			}

			try {
				sendInternal(m, c);
			} catch (IOException e) {
				// retry
				try {
					System.out.println("Sending failed. Retrying once.");
					sendInternal(m, c);
				} catch (IOException e1) {
					// remove, reroute, repair
					
					System.out.println("Sending failed. Repairing.");
					if (dest.getType() == RoutingNode.RoutingNodeType.LEAF) {
						leafs_.remove(dest);
						application.newLeaf( new LeafSet( leafs_ ) );
						routeAsync(m, c);
						requestLeafs(dest);
					} else if (dest.getType() == RoutingNode.RoutingNodeType.NEIGHBOR) {
						neighbors_.remove(dest);
						routeAsync(m, c);
						repairNeighbors(dest);
					} else if (dest.getType() == RoutingNode.RoutingNodeType.ROUTE) {
						routing_table_.remove(dest);
						routeAsync(m, c);
						repairRoutes(dest);
					}
//					e.printStackTrace();
				}
			}
		} else if (dest != null) {
			// process message
			if (m.type == PastryMessageType.USER) {
				application.deliver(m);
			} else {
				onDeliver(m, conn);
			}
		}

		return 0;
	}

	private int sendInternal(Message m, Connection c) throws IOException {
		Socket s = null;
		try {
			s = new Socket();
			s.setReuseAddress(true);
			//s.setSoLinger(true, 1);
			s.bind(new InetSocketAddress(0));
			System.out.println("FOOBAR:" + c.getIp() + " " + c.getPort());
			s.connect(new InetSocketAddress(c.getIp(), c.getPort()));

			if (m.from == null) {
				m.from = this.nodeID;
			}

			ObjectOutputStream os = new ObjectOutputStream(s.getOutputStream());
			os.writeObject(m);
			os.flush();
			os.close();
		} catch (IOException e) {
			throw e;
		} finally {
			if (s != null)
				s.close();
		}
		return 0;
	}

	/**
	 * Modifies a given message and executes side effects based on the message,
	 * if appropriate.
	 * 
	 * @param m
	 * @return
	 */
	private int onForward(Message m, Connection conn) {
		if (m.type == PastryMessageType.JOIN_REQUEST) {
			processJoinRequest(m, conn, false);
		}

		return 0;
	}

	/**
	 * Takes the appropriate action after receiving an internal message m
	 * 
	 * @param m
	 * @return
	 */
	private int onDeliver(Message m, Connection conn) {
		if (m.type == PastryMessageType.JOIN_REQUEST) {
			processJoinRequest(m, conn, true);
		} else if (m.type == PastryMessageType.JOIN_RESPONSE) {
			processJoinResponse(m, conn);
		} else if (m.type == PastryMessageType.ANNOUNCE) {
			processAnnounce(m, conn);
		} else if (m.type == PastryMessageType.HEARTBEAT) {
			processHeartbeat(m, conn);
		} else if (m.type == PastryMessageType.HEARTBEAT_RESPONSE) {
			processHeartbeatResponse(m, conn);
		} else if (m.type == PastryMessageType.REPAIR_LEAFS) {
			sendLeafs(m, conn);
		} else if (m.type == PastryMessageType.REPAIR_LEAFS_RESPONSE) {
			repairLeafs(m, conn);
		} else if (m.type == PastryMessageType.REPAIR_PING) {
			processRepairPing(m, conn);
		} else if (m.type == PastryMessageType.REPAIR_PING_RESPONSE) {
			processRepairPingResponse(m, conn);
		}
		return 0;
	}

	private void processJoinRequest(Message m, Connection conn,
			boolean send_leafs) {
		try {
			if (m.from.equals(nodeID))
				return;

			JoinRequest join = (JoinRequest) MessageSerializer
					.deserialize(m.data);

			if (join.hops == 0) {
				// set this node's ip if necessary
				if (this_node_.getIp() == null && join != null
						&& join.target != null) {
					this_node_.setIp(join.target);
					leafs_.setBaseIp(this_node_.getIp());
					routing_table_.setBaseNode(this_node_);
					application.newLeaf(new LeafSet(leafs_));
				}
				
				// this is the first node along the way;
				// correct origin ip if necessary
				InetAddress ip = conn.getIp();
				//int port = conn.getPort();

				if (ip != null && !ip.equals(join.origin.getIp())) {
					join.origin.setIp(ip);
				}

				//if (join.origin.getPort() != port) {
				//	join.origin.setPort(port);
				//}
			}

			join.hops = join.hops + 1;
			m.data = MessageSerializer.serialize(join);

			// send response
			JoinResponse resp = new JoinResponse();
			resp.hops = join.hops;
			resp.neighbors = new NeighborhoodSet(neighbors_);
			resp.routing_table = new RoutingTable(routing_table_);
			resp.target = new RoutingNode(join.origin);

			if (send_leafs) {
				resp.leafs = new LeafSet(leafs_);
			}

			Message response_message = new Message();
			response_message.data = MessageSerializer.serialize(resp);
			response_message.type = PastryMessageType.JOIN_RESPONSE;
			response_message.from = nodeID;
			response_message.to = join.origin.getId();

			Connection c = new Connection();
			c.setIp(join.origin.getIp());
			c.setPort(join.origin.getPort());
			sendInternal(response_message, c);
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}

	private void processJoinResponse(Message m, Connection conn) {
		JoinResponse join = null;

		try {
			join = (JoinResponse) MessageSerializer.deserialize(m.data);
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}

		// set this node's ip if necessary
		if (this_node_.getIp() == null && join != null
				&& join.target.getIp() != null) {
			this_node_.setIp(join.target.getIp());
			leafs_.setBaseIp(this_node_.getIp());
			routing_table_.setBaseNode(this_node_);
			application.newLeaf(new LeafSet(leafs_));
		}
		
		if (m.from.equals(nodeID))
			return;
		
		int hops = join.hops;
		RoutingNode[] row = join.routing_table.getRow(hops);

		if (row != null) {
			for (RoutingNode n : row) {
				routing_table_.insert(n);
			}
		}

		// TODO (Daniel) update neighborhood set
		// TODO (Daniel) update routing table based on node distance
		// TODO (Daniel) add the from node to the routing table

		// if this is from the numerically closest existing node
		if (join.leafs != null) {
			RoutingNode[] leafs = join.leafs.toArray();

			if (leafs != null) {
				for (RoutingNode n : leafs) {
					// TODO(Brian): hack for now/horribly exploitable
//					if (n.getIp() == null) {
//						n.setIp(conn.getIp());
//					}
					leafs_.insert(n);
				}
			}

			application.newLeaf(new LeafSet(leafs_));

			// yank the rest of the routing table
			for (int i = hops + 1; i < join.routing_table.rows(); i++) {
				row = join.routing_table.getRow(i);

				if (row != null) {
					for (RoutingNode n : row) {
						routing_table_.insert(n);
					}
				}
			}

			syncInitialState(join.leafs);
		}
	}

	/**
	 * Given a new node and its state, updates this node's state as necessary
	 * 
	 * @param m
	 * @param conn
	 */
	private void processAnnounce(Message m, Connection conn) {
		if (m.from.equals(nodeID))
			return;

		try {
			// TODO (Daniel) update neighborhood set and routing table
			StateMessage state = (StateMessage) MessageSerializer.deserialize(m.data);

			RoutingNode newb = new RoutingNode(m.from);
			newb.setIp(conn.getIp());
			newb.setPort(state.node.getPort());//conn.getPort());
			leafs_.insert(newb);
			routing_table_.insert(new RoutingNode(newb));

			application.newLeaf(new LeafSet(leafs_));

		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}

	private void processHeartbeat(Message m, Connection conn) {
		if (m.from.equals(nodeID)) {
			return;
		}

		Message response = new Message();
		response.to = m.from;
		response.from = nodeID;

		StateMessage state = new StateMessage();
		state.leafs = new LeafSet(leafs_);
		state.routing_table = new RoutingTable(routing_table_);
		state.neighbors = new NeighborhoodSet(neighbors_);

		try {
			response.data = MessageSerializer.serialize(state);
		} catch (IOException e) {

		}

		try {
			sendInternal(response, conn);
		} catch (IOException e) {
			// TODO repair
		}
	}

	private void processHeartbeatResponse(Message m, Connection conn) {
		if (m.from.equals(nodeID)) {
			return;
		}

		StateMessage state = null;

		try {
			state = (StateMessage) MessageSerializer.deserialize(m.data);
		} catch (IOException e) {

		} catch (ClassNotFoundException e) {

		}

		if (state != null) {
			// update leaf set
			if (state.leafs != null) {

				RoutingNode[] leafs = state.leafs.toArray();

				if (leafs != null) {
					for (RoutingNode leaf : leafs) {
						leafs_.insert(leaf);
					}
				}
			}

			// update routing table
			if (state.routing_table != null) {
				RoutingNode[] row = null;
				for (int i = 0; i < state.routing_table.rows(); i++) {
					row = state.routing_table.getRow(i);

					if (row != null) {
						for (RoutingNode n : row) {
							routing_table_.insert(n);
						}
					}
				}
			}

			// update neighborhood set
			if (state.neighbors != null) {
				RoutingNode[] neighbors = state.neighbors.toArray();

				if (neighbors != null) {
					for (RoutingNode neighbor : neighbors) {
						neighbors_.add(neighbor);
					}
				}
			}
		}
	}

	/**
	 * Announces arrival to the network. Called after join responses all come
	 * back.
	 */
	private void syncInitialState(LeafSet leafs) {
		// TODO(Daniel): update routing table based on node distance
		// TODO(Daniel): update neighborhood set
		// TODO(Daniel): add more threading

		if (leafs != null) {
			RoutingNode[] targets = leafs.toArray();

			if (targets != null) {
				for (RoutingNode t : targets) {
					if (nodeID.equals(t.getId()))
						continue;

					// send sync request
					Message m = new Message();
					m.from = nodeID;
					m.to = t.getId();
					m.type = PastryMessageType.ANNOUNCE;
					StateMessage state = new StateMessage();

					// TODO (Daniel) Make all state messages use copies of the
					// state
					state.leafs = new LeafSet(leafs_);
					state.neighbors = new NeighborhoodSet(neighbors_);
					state.routing_table = new RoutingTable(routing_table_);
					state.node = new RoutingNode(this_node_);

					try {
						m.data = MessageSerializer.serialize(state);
					} catch (IOException e) {
						e.printStackTrace();
					}

					Connection c = new Connection();
					c.setIp(t.getIp());
					c.setPort(t.getPort());

					routeInternal(m, c);
				}
			}
		}
	}
	
	/**
	 * Send leafs to another node, so that node can use it to repair its own leaf table
	 * @param m
	 * @param c
	 * @return
	 */
	private int sendLeafs(Message m, Connection c) {
		EmptySystemMessage origin = null;
		
		try {
			origin = (EmptySystemMessage) MessageSerializer.deserialize( m.data );
		} catch( IOException e ) {
			// ZOMG SPLODE
		} catch( ClassNotFoundException e ) {
			// OMG DOUBLE SPLODE
		}
		
		if( origin != null && origin.origin != null ) {
			StateMessage repair = new StateMessage( );
			repair.leafs = new LeafSet( leafs_ );
			
			Message mback = new Message();
			mback.from = nodeID;
			mback.to = origin.origin.getId();
			
			try {
				mback.data = MessageSerializer.serialize( repair );
			} catch( IOException e ) {
				// ASLDIF!
			}
			
			Connection cback = new Connection();
			cback.setIp(origin.origin.getIp());
			cback.setPort(origin.origin.getPort());
			
			try {
				sendInternal(mback, cback);
			} catch( IOException e ) {
				// Don't care
			}
		}
		
		return 0;
	}
	
	/**
	 * Repairs the leaf set after receiving new leafs from another node
	 * @param m
	 * @param c
	 * @return
	 */
	private int repairLeafs(Message m, Connection c) {
		StateMessage leafs = null;
		
		try {
			leafs = (StateMessage) MessageSerializer.deserialize(m.data);
		} catch (IOException e) {
			// don't worry
		} catch (ClassNotFoundException e) {
			// be happy
		}
		
		if( leafs != null && leafs.leafs != null ) {
			// repair!!!!
			for( RoutingNode nubbin : leafs.leafs.toArray() ) {
				if( nodeID.equals(nubbin.getId()) ) {
					continue;
				}
				
				nubbin.setType( RoutingNode.RoutingNodeType.LEAF );
				
				// ping them
				StateMessage ping = new StateMessage();
				ping.node = nubbin;
				
				Message pingm = new Message();
				pingm.from = nodeID;
				pingm.to = nubbin.getId();
				pingm.type = PastryMessageType.REPAIR_PING;
				
				try {
					pingm.data = MessageSerializer.serialize( ping );
					
					Connection pingc = new Connection();
					pingc.setIp( nubbin.getIp() );
					pingc.setPort( nubbin.getPort() );
					
					sendInternal(pingm, pingc);
				} catch( IOException e ) {
					///////NOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO
					return 1;
				}
//				leafs_.insert( nubbin );
			}
		}
		
		return 0;
	}
	
	private int processRepairPing(Message m, Connection c) {
		m.type = PastryMessageType.REPAIR_PING_RESPONSE;
		
		try {
			sendInternal(m, c);
		} catch( IOException e ) {
			// asefjawefij
			return 1;
		}
		
		return 0;
	}
	
	private int processRepairPingResponse(Message m, Connection c) {
		StateMessage state = null;
		
		try {
			state = (StateMessage) MessageSerializer.deserialize(m.data);
		} catch( IOException e ) {
			return 1;
		} catch( ClassNotFoundException e ) {
			return 1;
		}
		
		RoutingNode nub = state.node;
		
		if( nub != null ) {
			if( nub.getType() == RoutingNode.RoutingNodeType.LEAF ) {
				leafs_.insert( new RoutingNode( nub ) );
			} else if( nub.getType() == RoutingNode.RoutingNodeType.ROUTE ) {
				routing_table_.insert( new RoutingNode( nub ) );
			} else if( nub.getType() == RoutingNode.RoutingNodeType.NEIGHBOR ) {
				neighbors_.add( new RoutingNode( nub ) );
			}
		}
		
		return 0;
	}

	private int requestState(RoutingNode n) {
		Message m = new Message();
		m.from = nodeID;
		m.to = n.getId();
		m.type = PastryMessageType.HEARTBEAT;

		Connection c = new Connection();
		c.setIp(n.getIp());
		c.setPort(n.getPort());

		try {
			sendInternal(m, c);
		} catch (IOException e) {
			// TODO repair
		}

		return 0;
	}
	
	private void requestLeafs(RoutingNode dead) {
		RoutingNode savior = leafs_.closestNotBase(dead);
		Message repair = new Message( );
		repair.from = nodeID;
		
		if( savior != null ) {
		
			repair.to = savior.getId();
			
			Connection c = new Connection( );
			c.setIp( savior.getIp() );
			c.setPort( savior.getPort() );
			
			EmptySystemMessage origin = new EmptySystemMessage( new RoutingNode( this_node_ ) );
			
			try {
				repair.data = MessageSerializer.serialize( origin );
			} catch (IOException e ) {
				// ALEWFIJAWOIFJAWOEIFJ!!!!!!
			}
			
			repair.type = PastryMessageType.REPAIR_LEAFS;
			
			routeAsync(repair, c);
		} else {
			// TODO (Daniel) try repairing from routing table and neighborhood set
		}
	}
	
	private void repairNeighbors(RoutingNode dead) {
	}
	
	private void repairRoutes(RoutingNode dead) {
		String cp = nodeID.commonPrefix( dead.getId( ) );
		
		// the row dead was from
		int row = cp.length( );
		int col = -1;
		
		if( row + 1 < dead.getId( ).toString( routing_table_.base( ) ).length( ) ) {
			try	{
				// the digit in the
				// row + 1 position when rn.id is expressed in base 2^b.
				String converted_id = dead.getId( ).toString( routing_table_.base( ) );
				col = Integer.parseInt( converted_id.substring( row + 1, row + 2 ), routing_table_.base( ) );
			} catch( NumberFormatException e ) {
				return;
			}
		}
		
		RouteRepairMessage repair = new RouteRepairMessage( );
		repair.col = col;
		repair.row = row;
		repair.origin = new RoutingNode( this_node_ );
		repair.savior_row = row;
		repair.savior_col = 0;
		
		Message request = new Message( );
		request.from = nodeID;
		
		for( int i = repair.savior_col; i < routing_table_.cols(); i++ ) {
			
		}
	}

	private class ServerSocketThread implements Runnable, Serializable {
		private static final long serialVersionUID = -5935921463910730773L;
		transient ServerSocket s;

		public ServerSocketThread(ServerSocket sock) {
			this.s = sock;
		}

		public void run() {
			try {
				open = true;
				while (open) {
					try {
						Socket t = s.accept();
						ConnectionHandler h = new ConnectionHandler(t);
						new Thread(h).start();
					} catch (SocketTimeoutException ste) {
						// ignore
					} catch (IOException e) {
						e.printStackTrace();
						throw e;
					}
				}
			} catch (Exception e) {
				//
			} finally {
				try {
					s.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}

	private class ConnectionHandler implements Runnable, Serializable {
		private static final long serialVersionUID = -320728016484784672L;
		transient private Socket sock;

		public ConnectionHandler(Socket s) {
			this.sock = s;
		}

		public void run() {
			try {
				Connection c = new Connection(this.sock.getInetAddress(),
						this.sock.getPort());

				ObjectInputStream is = new ObjectInputStream(sock.getInputStream());
				Message m = (Message) is.readObject();
				is.close();
				routeInternal(m, c);
			} catch (IOException e) {
				e.printStackTrace();
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			} finally {
				try {
					sock.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}

	private class Heart extends TimerTask implements Serializable {
		private static final long serialVersionUID = -8695110021242351392L;

		public void run() {
			RoutingNode[] targets = leafs_.toArray();

			if (targets != null) {
				for (RoutingNode node : targets) {
					requestState(node);
				}
			}

			targets = neighbors_.toArray();

			if (targets != null) {
				for (RoutingNode node : targets) {
					requestState(node);
				}
			}
		}
	}
	
	private class StateSaver extends TimerTask implements Serializable {
		private static final long serialVersionUID = -8417937154359062162L;

		public void run() {
			try {
				FileOutputStream fos = new FileOutputStream(new File("node.sav"));
				ObjectOutputStream os = new ObjectOutputStream(fos);
				os.writeObject(PastryNode.node);
				os.flush();
				os.close();
				fos.close();
			} catch (IOException iex) {
				iex.printStackTrace();
			}
		}
	}
	
	private class Router implements Runnable, Serializable {
		private static final long serialVersionUID = 4893972554790975548L;
		transient private Message message_;
		transient private Connection conn_;
		
		public Router(Message m, Connection c) {
			message_ = m;
			conn_ = c;
		}
		
		public void run() {
			node.routeInternal(message_, conn_);
		}
	}
}
